//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd. or one of its subsidiaries.
//
// Garmin MapSource GDB waypoints/tracks export.
//

# include "StdAfx.h"
# include "GdbFile.h"
# include "MapSourceHelpers.h"
# include "Save.h"
# include "Map.h"
# include "Status.h"
# include "SaveFileDlg.h"
# include "FileWriter.h"
# include "fast_floor.h"
# include "Globals.h"

//////////////////////////////////////////

class CGdbExport : public CMapSaver {
  public:
	virtual bool CanSave (const map_t & _map) {return true;}
	virtual bool Save (HANDLE _hFile, const char * _strFileName, const map_t & _map);

  private:
	bool _Save (HANDLE _hFile, const map_t & _map);

	void _SaveWaypoint (CFileWriter & _fw, const rgn_t & _rgn, const rgn_origin_t  & _origin);
	void _SaveTrack    (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element);
	void _SaveRoute    (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element, bool _bElement0th);

	// Unique names for MapSource.
	names_t m_setNames;

	typedef std::map<const rgn_t *, string_t> rgn2name_t;
	rgn2name_t m_rgn2name;
};

DECLARE_WPTRK_EXPORT (CGdbExport, "garmin-gdb", "gdb", "Garmin GPS database (*.gdb)");

void CGdbExport::_SaveWaypoint (CFileWriter & _fw, const rgn_t & _rgn, const rgn_origin_t & _origin) {
	const waypoint_info_t * const pWptExtra = _rgn.pExtra->wtr.wpt ();

	// Generate unique name.
	string_t strName;
	GenerateUniqueNameForMapSource (_rgn.strLabel, m_setNames, strName);
	if (! (strName == _rgn.strLabel))
		m_rgn2name [& _rgn] = strName;

	const bool bHasAltitude = pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defAltitude);
	const size_t cAltitudeSize = bHasAltitude ? sizeof (BYTE) + sizeof (double) : sizeof (BYTE);

	const bool bHasProximity = pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defProximity);
	const size_t cProximitySize = bHasProximity ? sizeof (BYTE) + sizeof (double) : sizeof (BYTE);

	const bool bHasDepth = pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defDepth);
	const size_t cDepthSize = bHasDepth ? sizeof (BYTE) + sizeof (double) : sizeof (BYTE);

	const bool bHasTemperature = pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defTemperature);
	const size_t cTemperatureSize = bHasTemperature ? sizeof (BYTE) + sizeof (double) : sizeof (BYTE);

	const bool bHasTime = pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defTime);
	const size_t cTimeSize = bHasTime ? sizeof (BYTE) + sizeof (DWORD) : sizeof (BYTE);

	const size_t cRoutingDirectionsSize = pWptExtra ? pWptExtra->strRoutingDirections.size () + 1 : 1;
	const size_t cLinkSize              = pWptExtra ? pWptExtra->strURL.size ()               + 1 : 1;

	// Header.
	CGdbRecordHdr hdr;
	hdr.dwLen =
		strName.size () + 1         +
		sizeof (CGdbWaypoint2)      +
		cAltitudeSize               +
		_rgn.strComment.size () + 1 +
		cProximitySize              +
		sizeof (DWORD)              + // show
		sizeof (DWORD)              + // unknown
		sizeof (DWORD)              + // type
		sizeof (DWORD)              + // unknown
		cDepthSize                  +
		sizeof (WORD)               + // unknown
		sizeof (DWORD)              + // leg time
		cRoutingDirectionsSize      +
		cLinkSize                   +
		sizeof (WORD)               + // categories
		cTemperatureSize            +
		cTimeSize;
	hdr.btType = 'W';
	_fw.WriteBin (hdr);

	const size_t cPos = _fw.GetPos ();

	// Name.
	_fw.WriteS (strName);
	_fw.WriteC ('\0');

	CGdbWaypoint2 wp2;
	::memset (& wp2, 0, sizeof (wp2));
	wp2.dwMapObjectID = 0xFFFFFFFF;
	wp2.dwSubclass10  = 0xFFFFFFFF;
	wp2.dwSubclass11  = 0xFFFFFFFF;
	wp2.dwSubclass3   = 0xFFFFFFFF;

	// Position.
	wp2.lX = fast_floor (_origin.point.x*(1UL << 31)/180.f + .5f);
	wp2.lY = fast_floor (_origin.point.y*(1UL << 31)/180.f + .5f);
	_fw.WriteBin (wp2);

	// Altitude.
	if (bHasAltitude) {
		_fw.WriteBYTE (1);
		const double dfAltitude = pWptExtra->fAltitude;
		_fw.WriteBin (dfAltitude);
	} else
		_fw.WriteBYTE (0);

	// Comment.
	_fw.WriteS (_rgn.strComment);
	_fw.WriteC ('\0');

	// Proximity.
	if (bHasProximity) {
		_fw.WriteBYTE (1);
		const double dfProximity = pWptExtra->fProximity;
		_fw.WriteBin (dfProximity);
	} else
		_fw.WriteBYTE (0);

	// Show.
	DWORD dwShow = 1; // Show as 'symbol & name'.
	if (pWptExtra) {
		switch (pWptExtra->GetShow ()) {
			case waypoint_info_t::showHide:        dwShow = 1; break;
			case waypoint_info_t::showSymbol:      dwShow = 0; break;
			case waypoint_info_t::showName:        dwShow = 1; break;
			case waypoint_info_t::showDescription: dwShow = 2; break;
		}
	}
	_fw.WriteDWORD (dwShow);

	// Color.
	_fw.WriteDWORD (0);

	// Type.
	DWORD dwType;
	if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defGarminType))
		dwType = pWptExtra->wGarminType;
	else
		dwType = 0x12; // Plain waypoint.
	_fw.WriteDWORD (dwType);

	// ?? City+State+Facility
	_fw.WriteC ('\0');
	_fw.WriteC ('\0');
	_fw.WriteC ('\0');

	// ?? "Map line 8"
	_fw.WriteBYTE (0);

	// Depth.
	if (bHasDepth) {
		_fw.WriteBYTE (1);
		const double dfDepth = pWptExtra->fDepth;
		_fw.WriteBin (dfDepth);
	} else
		_fw.WriteBYTE (0);

	// Unknown.
	_fw.WriteWORD (0);

	// Auto-routing leg time.
	_fw.WriteDWORD (0);

	// Auto-routing directions.
	if (pWptExtra && ! pWptExtra->strRoutingDirections.empty ())
		_fw.WriteS (pWptExtra->strRoutingDirections);
	_fw.WriteC ('\0');

	// Link.
	if (pWptExtra && ! pWptExtra->strURL.empty ())
		_fw.WriteS (pWptExtra->strURL);
	_fw.WriteC ('\0');

	// Categories.
	_fw.WriteWORD (0);

	// Temperature.
	if (bHasTemperature) {
		_fw.WriteBYTE (1);
		const double dfTemperature = pWptExtra->fTemperature;
		_fw.WriteBin (dfTemperature);
	} else
		_fw.WriteBYTE (0);

	// Time.
	if (bHasTime) {
		_fw.WriteBYTE (1);
		const DWORD dwTime = fast_floor_d ((pWptExtra->dtTime - 25569.)*24.*60.*60. + .5);
		_fw.WriteBin (& dwTime, sizeof (dwTime));
	} else
		_fw.WriteBYTE (0);

	assert (_fw.GetPos () - cPos == hdr.dwLen);
}

void CGdbExport::_SaveTrack (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element) {
	const points_t & points = _element.points;
	const size_t cPoints = points.size ();

	const track_info_t * const pTrkExtra = _rgn.pExtra->wtr.track ();
	const rgn_element_extra_t * const pTrkExtra2 = _element.pExtra ? & * _element.pExtra : NULL;
	const size_t cPointsExtra = pTrkExtra ? pTrkExtra2->TrackPoints.size () : 0;

	// Header.
	CGdbRecordHdr hdr;
	hdr.dwLen = 0; // NOTE: to be fixed below.
	hdr.btType = 'T';
	const size_t cHdrPos = _fw.GetPos ();
	_fw.WriteBin (hdr);
	const size_t cDataPos = _fw.GetPos ();

	// Name.
	string_t strName;
	GenerateUniqueNameForMapSource (_rgn.strLabel, m_setNames, strName);
	_fw.WriteS (strName);
	_fw.WriteC ('\0');

	// Track header.
	CGdbTrack2 tr2;
	::memset (& tr2, 0, sizeof (tr2));
	tr2.btDisplayOnGPS = pTrkExtra && (pTrkExtra->wFlags & track_info_t::defHideInGPS) ? 0 : 1;
	tr2.dwColor        = pTrkExtra && (pTrkExtra->wFlags & track_info_t::defLineColor) ? FindNearestGarminTrackColor (pTrkExtra->colLine) : 0;
	tr2.dwPoints       = cPoints;
	_fw.WriteBin (& tr2, sizeof (tr2));

	for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
		// Position.
		const LONG lX = fast_floor (points [cPoint].x*(1UL << 31)/180.f + .5f);
		const LONG lY = fast_floor (points [cPoint].y*(1UL << 31)/180.f + .5f);
		_fw.WriteDWORD (lY);
		_fw.WriteDWORD (lX);

		if (pTrkExtra && cPoint < cPointsExtra) {
			const trackpoint_info_t & PointExtra = pTrkExtra2->TrackPoints [cPoint];

			// Altitude.
			if (PointExtra.wFlags & trackpoint_info_t::defAltitude) {
				_fw.WriteBYTE (1);
				const double dblAltitude = PointExtra.fAltitude;
				_fw.WriteBin (dblAltitude);
			} else
				_fw.WriteBYTE (0);

			// Time.
			if (PointExtra.wFlags & trackpoint_info_t::defTime) {
				_fw.WriteBYTE (1);
				const DWORD dwDateTime = fast_floor_d ((PointExtra.dtTime - 25569.)*24.*60.*60. + .5);
				_fw.WriteDWORD (dwDateTime);
			} else
				_fw.WriteBYTE (0);

			// Depth.
			if (PointExtra.wFlags & trackpoint_info_t::defDepth) {
				_fw.WriteBYTE (1);
				const double dblDepth = PointExtra.fDepth;
				_fw.WriteBin (dblDepth);
			} else
				_fw.WriteBYTE (0);

			// Temperature.
			if (PointExtra.wFlags & trackpoint_info_t::defTemperature) {
				_fw.WriteBYTE (1);
				const double dblTemperature = PointExtra.fTemperature;
				_fw.WriteBin (dblTemperature);
			} else
				_fw.WriteBYTE (0);
		}
	}

	// Link.
	if (pTrkExtra && ! pTrkExtra->strURL.empty ())
		_fw.WriteS (pTrkExtra->strURL);
	_fw.WriteC ('\0');

	// Fix the header.
	const size_t cPosEnd = _fw.GetPos ();
	_fw.SetPos (cHdrPos);
	hdr.dwLen = cPosEnd - cDataPos;
	_fw.WriteBin (hdr);
	_fw.SetPos (cPosEnd);

	assert (cPosEnd - cDataPos == hdr.dwLen);
}

void CGdbExport::_SaveRoute (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element, bool _bElement0th) {
	if (_bElement0th == false)
		return;

	const points_t & points = _element.points;
	const size_t cPoints = points.size ();

	const route_info_t * const pRtExtra = _rgn.pExtra->wtr.route ();
	const size_t cPointsExtra = pRtExtra ? pRtExtra->points.size () : 0;

	// Header.
	CGdbRecordHdr hdr;
	hdr.dwLen = 0; // NOTE: to be fixed below.
	hdr.btType = 'R';
	const size_t cHdrPos = _fw.GetPos ();
	_fw.WriteBin (hdr);
	const size_t cDataPos = _fw.GetPos ();

	// Name.
	string_t strName;
	GenerateUniqueNameForMapSource (_rgn.strLabel, m_setNames, strName);
	_fw.WriteS (strName);
	_fw.WriteC ('\0');

	// Auto-named.
	_fw.WriteWORD ((pRtExtra->wFlags & route_info_t::defAutoName) ? 1 : 0);

	// Maximum XYZ.
	const LONG lNorth = fast_floor (_rgn.rectBound.y1*(1UL << 31)/180.f + .5f);
	_fw.WriteDWORD (lNorth);
	const LONG lEast  = fast_floor (_rgn.rectBound.x1*(1UL << 31)/180.f + .5f);
	_fw.WriteDWORD (lEast);
	_fw.WriteBYTE (0);

	// Minimum XYZ.
	const LONG lSouth = fast_floor (_rgn.rectBound.y0*(1UL << 31)/180.f + .5f);
	_fw.WriteDWORD (lSouth);
	const LONG lWest  = fast_floor (_rgn.rectBound.x0*(1UL << 31)/180.f + .5f);
	_fw.WriteDWORD (lWest);
	_fw.WriteBYTE (0);

	size_t cLinks = 0;
	size_t cPoint;
	for (cPoint = 0; cPoint < cPointsExtra; ++ cPoint) {
		const routepoint_info_t & rpoint = pRtExtra->points [cPoint];
		if (rpoint.pWaypoint == NULL)
			continue;
		++ cLinks;
	}

	// Links.
	_fw.WriteDWORD (cLinks);
	for (cPoint = 0; cPoint < cPointsExtra; ++ cPoint) {
		const point_t & point = _element.points [cPoint];
		const routepoint_info_t & rpoint = pRtExtra->points [cPoint];
		const rgn_t * const pWpt = rpoint.pWaypoint;
		if (pWpt == NULL)
			continue;

		size_t cNextPoint;
		for (cNextPoint = cPoint + 1; cNextPoint < cPointsExtra; ++ cNextPoint)
			if (pRtExtra->points [cNextPoint].pWaypoint != NULL)
				break;
		if (cNextPoint > cPointsExtra)
			cNextPoint = cPointsExtra;

		const bool bLastLink = cNextPoint == cPointsExtra;

		if (pWpt) {
			rgn2name_t::const_iterator i = m_rgn2name.find (pWpt);
			_fw.WriteS (i == m_rgn2name.end () ? pWpt->strLabel : i->second);
		}
		_fw.WriteC ('\0');

		CGdbRouteLink2 rl2;
		::memset (& rl2, 0, sizeof (rl2));
		rl2.dwMapObjectID = 0xFFFFFFFF;
		rl2.dwSubclass10  = 0xFFFFFFFF;
		rl2.dwSubclass11  = 0xFFFFFFFF;
		rl2.dwSubclass3   = 0xFFFFFFFF;
		_fw.WriteBin (rl2);

		CGdbRouteLink3 rl3;
		::memset (& rl3, 0, sizeof (rl3));
		rl3.dwUnknown3    = 3;
		rl3.dwSubclass3   = 0xFFFFFFFF;
		rl3.dwPoints      = bLastLink ? 0 : 2;
		_fw.WriteBin (rl3);

		if (bLastLink) {
			// Separator.
			_fw.WriteBYTE (1);
		} else {
			// Link start point.
			const LONG lY0 = fast_floor (point.y*(1UL << 31)/180.f + .5f);
			_fw.WriteDWORD (lY0);
			const LONG lX0 = fast_floor (point.x*(1UL << 31)/180.f + .5f);
			_fw.WriteDWORD (lX0);
			_fw.WriteBYTE (0);

			// Link end point.
			assert (cNextPoint < cPointsExtra);
			const point_t & point1 = _element.points [cNextPoint];
			const LONG lY1 = fast_floor (point1.y*(1UL << 31)/180.f + .5f);
			_fw.WriteDWORD (lY1);
			const LONG lX1 = fast_floor (point1.x*(1UL << 31)/180.f + .5f);
			_fw.WriteDWORD (lX1);
			_fw.WriteBYTE (0);

			// Separator.
			_fw.WriteBYTE (0);

			// Max XYZ.
			_fw.WriteDWORD (__max (lY0, lY1));
			_fw.WriteDWORD (__max (lX0, lX1));
			_fw.WriteBYTE (0);

			// Min XYZ.
			_fw.WriteDWORD (__min (lY0, lY1));
			_fw.WriteDWORD (__min (lX0, lX1));
			_fw.WriteBYTE (0);
		}
	}

	// Link.
	if (pRtExtra && ! pRtExtra->strURL.empty ())
		_fw.WriteS (pRtExtra->strURL);
	_fw.WriteC ('\0');

	// Fix the header.
	const size_t cPosEnd = _fw.GetPos ();
	_fw.SetPos (cHdrPos);
	hdr.dwLen = cPosEnd - cDataPos;
	_fw.WriteBin (hdr);
	_fw.SetPos (cPosEnd);

	assert (cPosEnd - cDataPos == hdr.dwLen);
}

bool CGdbExport::Save (HANDLE _hFile, const char * _strFileName, const map_t & _map) {
	if (_hFile != NULL) {
		return _Save (_hFile, _map);
	} else {
		//
		// Prepare default file name.
		//
		char strFileName [MAX_PATH + 1];
		::strncpy (strFileName, _strFileName, MAX_PATH);
		strFileName [MAX_PATH] = '\0';
		if (! SaveFileDlg ("gdb", "Garmin GPS database (*.gdb)\0*.gdb\0", strFileName))
			return false;

		//
		// Open the file.
		//
		const CFileForWrite hFile (strFileName);
		if (! hFile.IsValid ()) {
			ErrorMsgBox ("Can't create file:\n %s", strFileName);
			return false;
		}

		return _Save (hFile, _map);
	}
}

bool CGdbExport::_Save (HANDLE _hFile, const map_t & _map) {
	CFileWriter fw (_hFile);

	m_setNames.clear ();

	SetProgressStatus (0);

	// Save header.
	fw.WriteBin (g_pGdbHeader, c_cGdbHeaderSize);

	// Save waypoints.
	size_t cPoint = 0;
	const layer_t & point_layer = _map.layers [rkPoint];
	const size_t cPoints = point_layer.cRgns;
	const rgns_t & points = point_layer.rgns;
	rgns_t::const_iterator iRgn;
	for (iRgn = points.begin (); iRgn != points.end (); ++ iRgn, ++ cPoint) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWaypoint ())
			continue;

		for (size_t cOrigin = 0; cOrigin < rgn.origins.size (); ++ cOrigin) {
			const rgn_origin_t & origin = rgn.origins [cOrigin];
			if (origin.bDeleted)
				continue;

			_SaveWaypoint (fw, rgn, origin);
		}

		SetProgressStatus (fast_floor (30.f*cPoint/cPoints + .5f));
	}

	// Save tracks and routes.
	size_t cPolyline = 0;
	const layer_t & polyline_layer = _map.layers [rkPolyline];
	const size_t cPolylines = polyline_layer.cRgns;
	const rgns_t & polylines = polyline_layer.rgns;
	for (iRgn = polylines.begin (); iRgn != polylines.end (); ++ iRgn, ++ cPolyline) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWTR ())
			continue;

		const bool bTrack = rgn.IsTrack ();

		for (size_t cElement = 0; cElement < rgn.elements.size (); ++ cElement) {
			const rgn_element_t & element = rgn.elements [cElement];
			if (element.bDeleted)
				continue;

			if (bTrack)
				_SaveTrack (fw, rgn, element);
			else {
				assert (rgn.IsRoute ());
				_SaveRoute (fw, rgn, element, cElement == 0);
			}
		}

		SetProgressStatus (30 + fast_floor (70.f*cPolyline/cPolylines + .5f));
	}

	// Save the tail record.
	fw.WriteBin (g_pGdbTail, c_cGdbTailSize);

	fw.FlushBuffer ();

	SetProgressStatus (100);

	if (fw.GetOverflow ())
		ErrorMsgBox ("File has been written partialy.\nThis may be caused by disk overflow.");

	return true;
}
